package com.monitor.methods;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.PortUnreachableException;
import java.net.ServerSocket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.channels.DatagramChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.BrokenBarrierException;

import jpcap.JpcapCaptor;
import jpcap.JpcapSender;
import jpcap.NetworkInterface;
import jpcap.NetworkInterfaceAddress;
import jpcap.packet.ARPPacket;
import jpcap.packet.DatalinkPacket;
import jpcap.packet.EthernetPacket;
import jpcap.packet.ICMPPacket;
import jpcap.packet.IPPacket;
import jpcap.packet.Packet;
import jpcap.packet.TCPPacket;
import jpcap.packet.UDPPacket;

import com.monitor.methods.Method;
import com.monitor.methods.tcpmethod.Scan;
import com.monitor.methods.udpmethod.UDPScan;
import com.monitor.persistence.Port;

public class UDPMethod implements Method {

	private InetAddress address;
	private int start;
	private int stop;
	private List<Port> values = new LinkedList<Port>();
	private NetworkInterface[] devices;
	private static int threadStopped = 0;
	private int threadAmount = 1;
	
	public UDPMethod(String address, int start, int stop)
			throws UnknownHostException {
		this.address = InetAddress.getByName(address);
		this.start = start;
		this.stop = stop;
	}

	@Override
	public void startMonitor() {
		devices = JpcapCaptor.getDeviceList();
		System.out.println("Wybierz interfejs: \n");
		for (int i = 0; i < devices.length; i++) {
			System.out.println(i + ": " + devices[i].name + "("
					+ devices[i].description + ")");

			System.out.println(" datalink: " + devices[i].datalink_name + "("
					+ devices[i].datalink_description + ")");

			System.out.print(" MAC address:");
			for (byte b : devices[i].mac_address)
				System.out.print(Integer.toHexString(b & 0xff) + ":");
			System.out.println();

			for (NetworkInterfaceAddress a : devices[i].addresses) {
				System.out.println(" address:" + a.address + " " + a.subnet
						+ " " + a.broadcast);
			}

			System.out.println();
		}
		Scanner sc = new Scanner(System.in);
		Integer interf = sc.nextInt();

		long timeStart = Calendar.getInstance().getTimeInMillis();
		byte[] destMac;
		try {
			System.out.println(address.getHostAddress());
			destMac = getMacAddress(interf);
			JpcapCaptor captor = JpcapCaptor.openDevice(devices[interf], 2000,
					false, 3000);
			 captor.setFilter("host "+address.getHostAddress(), true);
//			JpcapSender sender = captor.getJpcapSenderInstance();
			
			int difference = stop-start;
			if(difference < threadAmount)
				threadAmount = difference+1;
			for (int i=0; i < threadAmount; i++){
				UDPScan scan = new UDPScan(start+i*(difference)/threadAmount, start+(i+1)*(difference)/threadAmount, address, values, captor, devices[interf], destMac);
				Thread thread = new Thread(scan);
				thread.start();
			}
			while(true){
				if(threadStopped == threadAmount)
					break;
			}
			
			viewResults();
			System.out.println("Zajelo czasu: " + (Calendar.getInstance().getTimeInMillis()-timeStart)/1000+" sekund");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}


	private byte[] getMacAddress(Integer interf) throws IOException {

		JpcapCaptor captor = JpcapCaptor.openDevice(devices[interf], 2000,
				false, 3000);
		captor.setFilter("arp", true);

		JpcapSender sender = captor.getJpcapSenderInstance();

		ARPPacket arp = initArpPacket(interf);
		sender.sendPacket(arp);

		while (true) {
			ARPPacket p = (ARPPacket) captor.getPacket();

			if (p == null) {
				return new byte[] {(byte)0,(byte)6,(byte)7,(byte)8,(byte)9,(byte)10};
			}

			if (Arrays.equals(p.target_protoaddr,
					devices[interf].addresses[1].address.getAddress())
					&& Arrays.equals(p.sender_protoaddr, address.getAddress())) {
				sender.close();
				captor.close();
				return p.sender_hardaddr;
			}
		}

	}

	private ARPPacket initArpPacket(Integer interf) throws UnknownHostException {
		ARPPacket arp = new ARPPacket();
		byte[] broadcast = new byte[] { (byte) 255, (byte) 255, (byte) 255,
				(byte) 255, (byte) 255, (byte) 255 };

		arp.hardtype = ARPPacket.HARDTYPE_ETHER;
		arp.prototype = ARPPacket.PROTOTYPE_IP;
		arp.operation = ARPPacket.ARP_REQUEST;
		arp.hlen = 6;
		arp.plen = 4;

		arp.sender_hardaddr = devices[interf].mac_address;
		arp.sender_protoaddr = devices[interf].addresses[1].address
				.getAddress();
		arp.target_hardaddr = broadcast;
		arp.target_protoaddr = address.getAddress();

		EthernetPacket ether = new EthernetPacket();
		ether.frametype = EthernetPacket.ETHERTYPE_ARP;
		ether.src_mac = devices[interf].mac_address;
		ether.dst_mac = broadcast;

		arp.datalink = ether;

		return arp;
	}

	private void viewResults() {
		System.out.println("Porty: ");
		for (Port port : values) {
			if(port.getState().compareTo("closed") != 0)
				System.out.println("Port " + port.getNumber()+"\t "+port.getState());
		}
	}
	
	public synchronized static void signThreadStopped(){
		threadStopped++;
	}
}
